package gcvalid

import (
	"encoding/json"
	"fmt"
	"io"
	"reflect"
	"regexp"
	"strings"

	"gitee.com/githubdog/goci/gclang"
	"github.com/go-playground/validator/v10"
)

/*

使用方法

var (
	form   formvalid.xxx
)

if err := c.ShouldBindJSON(&form); err == nil {
	// 验证成功
} else {
	ces := formvalid.CustomErrStruct{
		StructPtr: &form,
		OnlyFirst: false,
	}
	resObj.Message = ces.ErrorMsg(err)
	log.Printf("form: %+v\n", form)
	log.Printf("[%s]ShouldBindJSON Fail: %s\n", c.ClientIP(), err.Error())
	c.JSON(http.StatusOK, resObj)
}

*/

// CustomErrFunc 自定义错误函数
type CustomErrFunc func(fe validator.FieldError) string

// CusValidFunc 自定义验证函数
type CusValidFunc func(fl validator.FieldLevel) bool

// CustomErrMap 自定义错误字典
type CustomErrMap struct {
	Texts map[string]string // 格式: [Struct Field]_[binding Tag]_[lang?], eg: Name_required / Name_required_zh
	Funcs map[string]CustomErrFunc
	Names map[string]string
}

// CustomErrStruct 自定义错误结构体
type CustomErrStruct struct {
	StructPtr interface{}
	Map       CustomErrMap
	OnlyFirst bool
	JoinStr   string
}

func getFieldName(ce CustomErrMap, field string, rt reflect.Type) string {
	if ce.Names != nil {
		if val, ok := ce.Names[field]; ok {
			return val
		}
	}
	if stFidle, ok := rt.FieldByName(field); ok {
		if title := stFidle.Tag.Get("title"); title != "" {
			field = title
		}
	}
	return field
}

var fieldErrMsg = "Key: '%s' Error:Field validation for '%s' failed on the '%s' tag"

//Tag: gt
//ActualTag: gt
//Field: unique_id / UniqueID
//Kind: string
//Namespace: Install.UniqueID
//Param: 6
//structField: UniqueID
//StructNamespace: Install.UniqueID
//Type: string
//Value: a

var regInt = regexp.MustCompile(`^u?int\d*$`)

// ErrorMsg 错误信息
// [特定] 先从 ces.CustomErrMap 中获取错误: Funcs优化级高于Texts
// [公共] 若未获取到错误信息, 再从lang语言包中获取
func (ces *CustomErrStruct) ErrorMsg(_lang string, err error) string {
	ret := ""
	if err == nil {
		return ret
	} else if err == io.EOF {
		ret = gclang.GetNowLangValue(_lang, "comm_err_EOF")
	} else if vErrs, ok := err.(validator.ValidationErrors); ok {
		errs := []string{}
		rt := reflect.TypeOf(ces.StructPtr)
		isLegalStruct := false // 是否为合法结构体
		if rt.Kind() == reflect.Ptr {
			rt = rt.Elem()
			isLegalStruct = true
		}

		for _, fe := range vErrs {
			item := ""
			field := fe.Field()
			if isLegalStruct {
				field = getFieldName(ces.Map, field, rt)
				if _field := gclang.GetNowLangValue(_lang, formatLangKeyword(field)); _field != "" {
					field = _field
				}
			}
			eTag1 := fe.Tag()
			eTagArr := []string{eTag1}
			eTag2 := strings.ReplaceAll(eTag1, "len=0|", "")
			eTag2 = strings.ReplaceAll(eTag2, "|len=0", "")
			if eTag2 != eTag1 {
				eTagArr = append(eTagArr, eTag2)
			}
			if index := strings.Index(eTag2, "="); index > -1 {
				eTagArr = append(eTagArr, eTag2[:index])
			}
			if strings.HasPrefix(eTag1, "required_") {
				eTagArr = append(eTagArr, "required")
			}
			if ces.Map.Texts != nil {
				eTagArr2 := []string{}
				for _, eTag := range eTagArr {
					eTagArr2 = append(eTagArr2, fmt.Sprintf("%s_%s", fe.StructField(), eTag))
					eTagArr2 = append(eTagArr2, fmt.Sprintf("%s_%s_%s", fe.StructField(), eTag, _lang))
				}
				for _, eTag := range eTagArr2 {
					if fn, ok := ces.Map.Funcs[eTag]; ok {
						item = fn(fe)
					} else if val, ok := ces.Map.Texts[eTag]; ok {
						item = val
					}
					if item != "" {
						break
					}
				}
			}
			if item == "" {
				tStr := fe.Type().String()
				if m := regInt.FindString(tStr); m != "" {
					tStr = "int"
				}
				for _, eTag := range eTagArr {
					if item = gclang.GetNowLangValue(_lang, fmt.Sprintf("comm_formvalid.%s_%s", fe.StructField(), eTag)); item == "" {
						if item = gclang.GetNowLangValue(_lang, fmt.Sprintf("comm_formvalid.%s_%s", tStr, eTag)); item == "" {
							item = gclang.GetNowLangValue(_lang, "comm_formvalid."+eTag)
						}
					}
					if item != "" {
						break
					}
				}
				if item != "" {
					if _item := gclang.GetNowLangValue(_lang, formatLangKeyword(item)); _item != "" {
						item = _item
					}
				} else {
					item = fmt.Sprintf(fieldErrMsg, fe.Namespace(), field, eTag1)
				}
			}
			item = strings.ReplaceAll(item, "{field}", field)
			paramName := getFieldName(ces.Map, fe.Param(), rt)
			if _paramName := gclang.GetNowLangValue(_lang, formatLangKeyword(paramName)); _paramName != "" {
				paramName = _paramName
			}
			item = strings.ReplaceAll(item, "{param}", paramName)
			if ces.OnlyFirst {
				return item
			}
			errs = append(errs, item)
		}
		ret = strings.Join(errs, ces.JoinStr)
	} else if ute, ok := err.(*json.UnmarshalTypeError); ok {
		typeErr := gclang.GetNowLangValue(_lang, "comm_form_err_type")
		if val, ok := ces.Map.Texts[ute.Field+"_jsonTypeError"]; ok {
			typeErr = val
		}
		//field => [unique_id]
		//offset => [20]
		//struct => [Install]
		//type => [string]
		//value => [number]
		ret = strings.ReplaceAll(typeErr, "{field}", ute.Field)
		ret = strings.ReplaceAll(ret, "{type}", ute.Type.String())
	} else if _, ok := err.(*json.SyntaxError); ok {
		ret = gclang.GetNowLangValue(_lang, "comm_form_not_json")
	} else {
		ret = err.Error()
	}
	return ret
}

func formatLangKeyword(key string) string {
	if strings.HasPrefix(key, "<") && strings.HasSuffix(key, ">") {
		key = key[1:]
		key = key[:len(key)-1]
	}
	return key
}
